import java.io.*;
import java.net.*;
import java.util.*;
import org.xml.sax.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import org.xml.sax.helpers.XMLReaderFactory;

class ServiceInterface implements Runnable {
	private Socket client;
	private String log_file = "log/status.log_" + getDate();
	private Hashtable request_ht = new Hashtable();
	private Hashtable response_ht = new Hashtable();
	private String message = "";	

	ServiceInterface(Socket client) {
		this.client = client;
	}

	private String getDate() {
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		Date date = new Date();
		return dateFormat.format(date);
	}

	private String getDateTime() {
		DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
		Date date = new Date();
		return dateFormat.format(date);
	}

	public void run(){
		String line = "";
		BufferedReader in = null;
		PrintWriter out = null;
		BufferedWriter log_writer = null;
		try{
			log_writer = new BufferedWriter(new FileWriter(log_file,true));
			in = new BufferedReader(new InputStreamReader(client.getInputStream()));
			out = new PrintWriter(client.getOutputStream(), true);
		} catch (IOException e) {
			try {
				log_writer.write("Failed to establish input and output streams."); log_writer.newLine();
				client.close();
			} catch (IOException ex) {
			}
		}
		request_ht.put("Content-Length",0);
		Vector input_lines = read_lines(in,log_writer);
		int http_code = process_input(input_lines);
		System.out.println("Content length: " + request_ht.get("Content-Length"));
		out.println("HTTP/1.1 100 Continue"); 
		out.println("");
		String data_lines = read_body(in,log_writer);
		System.out.println("=======");
		System.out.println(data_lines);
		System.out.println("=======");
		if (http_code == 100) {
			http_code = process_content(data_lines,out,log_writer);
			if (http_code != 200) {
				out.println("HTTP/1.1 400 Bad Request");
				outputResponse(http_code,out);
			}
		} else {
			http_code = header_message(http_code,out);	
		} 
		try {
			client.close();	
			log_writer.close();
		} catch (IOException s_error) {	
			s_error.printStackTrace();
		}
	}

	public int process_content(String input_lines, PrintWriter out, BufferedWriter log_writer) {
		String soap_action = (String)request_ht.get("SOAPAction");
		soap_action = soap_action.trim();
		if (soap_action.substring(0,1).equals("\"")) {
			soap_action = soap_action.substring(1,soap_action.length()-1);
		}
		try {
			soap_action = soap_action.substring(0,soap_action.indexOf("#"));
		} catch (Exception e) {}
		try {
			XMLReader parser = XMLReaderFactory.createXMLReader();
			parser.setContentHandler(new PronomXMLContentHandler(out,log_writer,soap_action));
			parser.parse(new InputSource(new StringReader(input_lines)));
		} catch (Exception e) {
			message = "<b>Failed to parse input document</b><br/><br/>" + e.getMessage();
			return 400;
		}
		return 200;
	}
	
	public String read_body(BufferedReader in, BufferedWriter log_writer) {
		int clength = Integer.parseInt(request_ht.get("Content-Length").toString().trim());
		int ccount = 0;
		String body = "";
		while (ccount<clength) {
			try {
				char current = (char)in.read();
				body += current;
				ccount++;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return body;
	}
		
	public Vector read_lines(BufferedReader in, BufferedWriter log_writer) {
		String line = "";
		Vector input_lines = new Vector();
		int chars = 0;
		try {
			log_writer.write("Processing Request from " + client.getInetAddress().toString() + " (" + getDateTime() + ")"); log_writer.newLine();
			line = in.readLine();
		} catch (IOException e) {
			try {
				log_writer.write("Read from " + client.getInetAddress().toString() +" at " + getDateTime() + " failed"); log_writer.newLine();
				client.close();
			} catch (IOException ex) {
			}
		}
		while(!line.equals("")){
			try{
				input_lines.add(line);
				log_writer.write(line); log_writer.newLine();
				line = in.readLine();
			} catch (IOException e) {
				try {
					log_writer.write("Read from " + client.getInetAddress().toString() +" at " + getDateTime() + " failed"); log_writer.newLine();
					client.close();
				} catch (IOException ex) {
				}
			}
		}
		return input_lines;
	}

	private int process_input(Vector input_lines) {
		String line = (String)input_lines.get(0);
		System.out.println(line);
		String[] request = line.split(" ");
		if (request[0].equalsIgnoreCase("HEAD") || request[0].equalsIgnoreCase("GET") || request[0].equalsIgnoreCase("POST") || request[0].equalsIgnoreCase("PUT") || request[0].equalsIgnoreCase("DELETE") || request[0].equalsIgnoreCase("TRACE") || request[0].equalsIgnoreCase("OPTIONS") || request[0].equalsIgnoreCase("CONNECT")) {
			request_ht.put("type",request[0]);
			request_ht.put("uri",request[1]);
			request_ht.put("protocal",request[2]);
			Enumeration e = input_lines.elements();
			e.nextElement();
			while (e.hasMoreElements()) {
				line = (String)e.nextElement();
				System.out.println(line);
				request = line.split(":",2);	
				request_ht.put(request[0],request[1]);
			}
			try {
				if (request_ht.get("uri").toString().equalsIgnoreCase("/pronom/service.asmx")) {
					response_ht.put("location","http://nationalarchives.gov.uk"+request_ht.get("uri"));
					return 307;
				}
				if (!request_ht.get("uri").toString().equalsIgnoreCase("/pronom/preservationplanning.asmx")) {
					return 404;
				}
				//if (!request_ht.get("Content-type").toString().trim().equalsIgnoreCase("text/xml; charset=utf-8")) {
				//	return 415;
				//}
				if (!request_ht.get("SOAPAction").toString().equals("")) {
					String soap_action = (String)request_ht.get("SOAPAction");
					soap_action = soap_action.trim();
					if (soap_action.substring(0,1).equals("\"")) {
						soap_action = soap_action.substring(1,soap_action.length()-1);
					}
					try {
						soap_action = soap_action.substring(0,soap_action.indexOf("#"));
					} catch (Exception e3n) {};
					System.out.println("SOAPAction = " + soap_action);
					if (soap_action.equalsIgnoreCase("http://pp.pronom.nationalarchives.gov.uk/getFormatPropertyRiskIn") || soap_action.equalsIgnoreCase("http://pp.pronom.nationalarchives.gov.uk/getFormatRiskIn")) {
						return 100;
					} 
				} else {
					return 400;
				}
				System.out.println("MAJOR ERROR IN REQUEST HANDLING ROUTINE");
			} catch (Exception ex) {
				return 400;
			}
			return 400;
		} else {
			return 400;
		}
	}
	private int header_message(int http_code, PrintWriter out) {
		switch (http_code) {
			case 100: out.println("HTTP/1.1 100 Continue"); outputResponse(100,out); break;
			case 200: out.println("HTTP/1.1 200 OK"); break;
			case 302: out.println("HTTP/1.1 302 Found"); outputResponse(302,out); break;
			case 307: out.println("HTTP/1.1 307 Temporary Redirect"); outputResponse(307,out); break;
			case 404: out.println("HTTP/1.1 404 Not Found");
			case 415: out.println("HTTP/1.1 415 Unsupported Media Type");
			default: out.println("HTTP/1.1 400 Bad Request"); break;
		}
		//out.println("Date: " + getDateTime());
		//out.println("Server: Service Controller");
		//out.println("X-Powered-By: Java");
		//out.println("Connection: close");
		//out.println("Content-Type: text/xml; charset=utf-8");
		out.println("");
		return http_code;
	}
	private void outputResponse(int http_code,PrintWriter out) {
		out.println("Date: " + getDateTime());
		out.println("Server: Service Controller");
		out.println("X-Powered-By: Java");
		out.println("Connection: close");
		if (http_code == 302 || http_code == 307) {
			out.println("Location: " + response_ht.get("location"));
		}
		if (!message.equals("")) {
			out.println("Content-Type: text/html; charset=utf-8");
			out.println("Content-Length: " + message.toCharArray().length);
			out.println("");
			out.println(message);
			System.out.println(message);
		} else {
			out.println("Content-Type: text/xml; charset=utf-8");
			out.println("Content-Length: 0");
		}
	}
}

class SocketThrdServer {

	ServerSocket server = null;

	SocketThrdServer(){
		/**
		 * Constructor
		 * In here you can do things like construct a UI or something, but since each thread will handle things differently we don't want a master interfacce.
		 */
	} 

	public void listenSocket(int port){
		try{
			server = new ServerSocket(port);
			System.out.println("Listening on port " + port);
		} catch (IOException e) {
			System.out.println("Could not listen on port " + port);
			System.exit(-1);
		}
		while(true){
			ServiceInterface si;
			try{
				si = new ServiceInterface(server.accept()) ;
				Thread sit = new Thread(si);
				sit.start();
			} catch (IOException e) {
				System.out.println("Accept failed: " + port);
				System.exit(-1);
			}
		}
	}

	protected void finalize(){
		//Objects created in run method are finalized when 
		//program terminates and thread exits
		try{
			server.close();
		} catch (IOException e) {
			System.out.println("Could not close socket");
			System.exit(-1);
		}
	}

	public static void main(String[] args){
		int port = 4444;
		try {
			if (args[0].equals("--help")) {
				System.out.println("Service Interface Listener (v0.1nea)");
				System.out.println("");
				System.out.println("Usage:");
				System.out.println("");
				System.out.println(" -p    Port Number to Listen On");
				System.out.println("");
				System.out.println("");
				System.exit(0);
			} else {
				while (args.length > 0) {
					if (args[0].equals("-p")) {
						port = Integer.parseInt(args[1]);
					}
					if (args.length > 2) {
						String[] foo = new String[args.length-2];
						for (int i=2;i<args.length;i++) {
							foo[i-2] = args[i];
						}
						args = foo;
					} else {
						args = new String[0];
					}
				}
			}
		} catch ( Exception e) {
		}
		SocketThrdServer sts = new SocketThrdServer();
		sts.listenSocket(port);
	}
}

